//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include <TargetConditionals.h>
#include <Availability.h>
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#include <dispatch/dispatch.h>
#include <os/base.h>
#include <os/log.h>
#include <objc/runtime.h>
#include <wchar.h>

#ifndef os_fastpath
#define os_fastpath(x) ((__typeof__(x))OS_EXPECT((long)(x), ~0l))
#endif
#ifndef os_slowpath
#define os_slowpath(x) ((__typeof__(x))OS_EXPECT((long)(x), 0l))
#endif
#ifndef os_likely
#define os_likely(x) OS_EXPECT(!!(x), 1)
#endif
#ifndef os_unlikely
#define os_unlikely(x) OS_EXPECT(!!(x), 0)
#endif

#ifndef MIN
#define MIN(a, b)  (((a)<(b))?(a):(b))
#endif

#define OST_FORMAT_MAX_STRING_SIZE 1024

#define OS_LOG_PRIVACY_OPTION_DEFAULT 0
#define OS_LOG_PRIVACY_OPTION_PRIVATE 1
#define OS_LOG_PRIVACY_OPTION_PUBLIC 2

enum os_trace_int_types_t {
  T_CHAR = -2,
  T_SHORT = -1,
  T_INT = 0,
  T_LONG = 1,
  T_LONGLONG = 2,
  T_SIZE = 3,
  T_INTMAX = 4,
  T_PTRDIFF = 5,
};

OS_ENUM(os_log_value_type, uint8_t,
  OS_LOG_BUFFER_VALUE_TYPE_SCALAR = 0,
  OS_LOG_BUFFER_VALUE_TYPE_COUNT = 1,
  OS_LOG_BUFFER_VALUE_TYPE_STRING = 2,
  OS_LOG_BUFFER_VALUE_TYPE_POINTER = 3,
  OS_LOG_BUFFER_VALUE_TYPE_OBJECT = 4,
);

OS_ENUM(os_log_value_subtype, uint8_t,
  OS_LOG_BUFFER_VALUE_SUBTYPE_NONE = 0,
  OS_LOG_BUFFER_VALUE_SUBTYPE_INTEGER = 1,
  OS_LOG_BUFFER_VALUE_SUBTYPE_FLOAT = 2,
);

enum os_log_int_types_t {
  OST_CHAR = -2,
  OST_SHORT = -1,
  OST_INT =  0,
  OST_LONG =  1,
  OST_LONGLONG =  2,
  OST_SIZE =  3,
  OST_INTMAX =  4,
  OST_PTRDIFF =  5,
};

union os_log_format_types_u {
  uint16_t u16;
  uint32_t u32;
  uint64_t u64;
  char ch;
  short s;
  int i;
  void *p;
  char *pch;
  wchar_t wch;
  wchar_t *pwch;
  size_t z;
  intmax_t im;
  ptrdiff_t pd;
  long l;
  long long ll;
  double d;
  float f;
  long double ld;
};

typedef struct os_log_format_value_s {
  union os_log_format_types_u type;
  os_log_value_type_t ctype;
  uint16_t size;
} *os_log_format_value_t;

typedef struct os_log_buffer_value_s {
#define OS_LOG_CONTENT_FLAG_PRIVATE 0x1
#define OS_LOG_CONTENT_FLAG_PUBLIC 0x2
  uint8_t flags : 4;
  os_log_value_type_t type : 4;
  uint8_t size;
  uint8_t value[];
} *os_log_buffer_value_t;

typedef struct os_log_buffer_s {
#define OS_LOG_BUFFER_HAS_PRIVATE 0x1
#define OS_LOG_BUFFER_HAS_NON_SCALAR 0x2
#define OS_LOG_BUFFER_MAX_SIZE 1024
  uint8_t flags;
  uint8_t arg_cnt;
  uint8_t content[];
} *os_log_buffer_t;

typedef struct os_log_buffer_context_s {
  os_log_t log;
  os_log_buffer_t buffer;

  // sizes and offsets
  uint16_t content_off; // offset into buffer->content
  uint16_t content_sz; // size not including the header
  uint8_t arg_idx;
} *os_log_buffer_context_t;

static bool
_os_log_encode_arg(const void *arg, uint16_t arg_len, os_log_value_type_t ctype, uint8_t flags, os_log_buffer_context_t context)
{
  os_log_buffer_value_t content = (os_log_buffer_value_t) &context->buffer->content[context->content_off];
  size_t content_sz = sizeof(*content) + arg_len;

  content->type = ctype;
  content->flags = flags;

  switch (ctype) {
    case OS_LOG_BUFFER_VALUE_TYPE_COUNT:
    case OS_LOG_BUFFER_VALUE_TYPE_SCALAR:
      if ((context->content_off + content_sz) > context->content_sz) {
        return false;
      }

      memcpy(content->value, arg, arg_len);
      content->size = arg_len;
      context->content_off += content_sz;
      break;

    case OS_LOG_BUFFER_VALUE_TYPE_STRING:
    case OS_LOG_BUFFER_VALUE_TYPE_POINTER:
    case OS_LOG_BUFFER_VALUE_TYPE_OBJECT:
      memcpy(content->value, arg, arg_len);
      context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
      content->size = arg_len;
      context->content_off += content_sz;
      break;

  }

  if (content->flags & OS_LOG_CONTENT_FLAG_PRIVATE) {
    context->buffer->flags |= OS_LOG_BUFFER_HAS_PRIVATE;
  }

  context->arg_idx++;

  return true;
}

static bool
_os_log_encode(const char *format, va_list args, int saved_errno, os_log_buffer_context_t context)
{
  const char *percent = strchr(format, '%');

  while (percent != NULL) {
    ++percent;
    if (percent[0] != '%') {
      struct os_log_format_value_s value;
      uint8_t flags = 0;
      int type = T_INT;
      bool long_double = false;
      int prec = 0;
      char ch;

      for (bool done = false; !done; percent++) {
        switch (ch = percent[0]) {
          /* type of types or other */
          case 'l': // longer
            type++;
            break;

          case 'h': // shorter
            type--;
            break;

          case 'z':
            type = T_SIZE;
            break;

          case 'j':
            type = T_INTMAX;
            break;

          case 't':
            type = T_PTRDIFF;
            break;

          case '.': // precision
            if ((percent[1]) == '*') {
              prec = va_arg(args, int);
              _os_log_encode_arg(&prec, sizeof(prec), OS_LOG_BUFFER_VALUE_TYPE_COUNT, flags, context);
              percent++;
              continue;
            } else {
              // we have to read the precision and do the right thing
              const char *fmt = percent + 1;
              prec = 0;
              while (isdigit(ch = *fmt++)) {
                prec = 10 * prec + (ch - '0');
              }

              if (prec > 1024) {
                prec = 1024;
              }

              _os_log_encode_arg(&prec, sizeof(prec), OS_LOG_BUFFER_VALUE_TYPE_COUNT, flags, context);
            }
            break;

          case '-': // left-align
          case '+': // force sign
          case ' ': // prefix non-negative with space
          case '#': // alternate
          case '\'': // group by thousands
            break;

          case '{': // annotated symbols
            for (const char *curr2 = percent + 1; (ch = (*curr2)) != 0; curr2++) {
              if (ch == '}') {
                if (strncmp(percent + 1, "private", MIN(curr2 - percent - 1, 7)) == 0) {
                  flags |= OS_LOG_CONTENT_FLAG_PRIVATE;
                }
                percent = curr2;
                break;
              }
            }
            break;

            /* fixed types */
          case 'd': // integer
          case 'i': // integer
          case 'o': // octal
          case 'u': // unsigned
          case 'x': // hex
          case 'X': // upper-hex
            switch (type) {
              case T_CHAR:
                value.type.ch = va_arg(args, int);
                _os_log_encode_arg(&value.type.ch, sizeof(value.type.ch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              case T_SHORT:
                value.type.s = va_arg(args, int);
                _os_log_encode_arg(&value.type.s, sizeof(value.type.s), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              case T_INT:
                value.type.i = va_arg(args, int);
                _os_log_encode_arg(&value.type.i, sizeof(value.type.i), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              case T_LONG:
                value.type.l = va_arg(args, long);
                _os_log_encode_arg(&value.type.l, sizeof(value.type.l), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              case T_LONGLONG:
                value.type.ll = va_arg(args, long long);
                _os_log_encode_arg(&value.type.ll, sizeof(value.type.ll), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              case T_SIZE:
                value.type.z = va_arg(args, size_t);
                _os_log_encode_arg(&value.type.z, sizeof(value.type.z), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              case T_INTMAX:
                value.type.im = va_arg(args, intmax_t);
                _os_log_encode_arg(&value.type.im, sizeof(value.type.im), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              case T_PTRDIFF:
                value.type.pd = va_arg(args, ptrdiff_t);
                _os_log_encode_arg(&value.type.pd, sizeof(value.type.pd), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
                break;

              default:
                return false;
            }
            done = true;
            break;

          case 'P': // pointer data
            if (prec > 0) { // only encode a pointer if we have been given a length
              context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
              value.type.p = va_arg(args, void *);

              _os_log_encode_arg(value.type.p, prec, OS_LOG_BUFFER_VALUE_TYPE_POINTER, flags, context);
              prec = 0;
              done = true;
            }
            break;

          case 'L': // long double
            long_double = true;
            break;

          case 'a': case 'A': case 'e': case 'E': // floating types
          case 'f': case 'F': case 'g': case 'G':
            if (long_double) {
              value.type.ld = va_arg(args, long double);
              _os_log_encode_arg(&value.type.ld, sizeof(value.type.ld), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
            } else {
              value.type.d = va_arg(args, double);
              _os_log_encode_arg(&value.type.d, sizeof(value.type.d), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
            }
            done = true;
            break;

          case 'c': // char
            value.type.ch = va_arg(args, int);
            _os_log_encode_arg(&value.type.ch, sizeof(value.type.ch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
            done = true;
            break;

          case 'C': // wide-char
            value.type.wch = va_arg(args, wint_t);
            _os_log_encode_arg(&value.type.wch, sizeof(value.type.wch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
            done = true;
            break;

#if 0
          // String types get sent from Swift as NSString objects.
          case 's': // string
            value.type.pch = va_arg(args, char *);
            context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
            _os_log_encode_arg(&value.type.pch, sizeof(value.type.pch), OS_LOG_BUFFER_VALUE_TYPE_STRING, flags, context);
            prec = 0;
            done = true;
            break;
#endif

          case '@': // CFTypeRef aka NSObject *
            value.type.p = va_arg(args, void *);
            context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
            _os_log_encode_arg(&value.type.p, sizeof(value.type.p), OS_LOG_BUFFER_VALUE_TYPE_OBJECT, flags, context);
            done = true;
            break;

          case 'm':
            value.type.i = saved_errno;
            _os_log_encode_arg(&value.type.i, sizeof(value.type.i), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, flags, context);
            done = true;
            break;

          default:
            if (isdigit(ch)) { // [0-9]
              continue;
            }
            return false;
        }

        if (done) {
          percent = strchr(percent, '%'); // Find next format
          break;
        }
      }
    } else {
      percent = strchr(percent+1, '%'); // Find next format after %%
    }
  }

  context->buffer->arg_cnt = context->arg_idx;
  context->content_sz = context->content_off;
  context->arg_idx = context->content_off = 0;

  return true;
}

__attribute__((__visibility__("default")))
extern "C" void
_swift_os_log(void *dso, os_log_t oslog, os_log_type_t type, const char *format, va_list args)
{
  struct os_log_buffer_context_s context = { 0, 0, 0, 0, 0 };
  os_log_buffer_t buffer = (os_log_buffer_t)alloca(OS_LOG_BUFFER_MAX_SIZE);
  int save_errno = errno; // %m

  memset(buffer, 0, OS_LOG_BUFFER_MAX_SIZE);

  context.buffer = buffer;
  context.content_sz = OS_LOG_BUFFER_MAX_SIZE - sizeof(*buffer);

  if (_os_log_encode(format, args, save_errno, &context)) {
    _os_log_impl(dso, oslog, type, format, (uint8_t *)buffer, context.content_sz);
  }
}
